#include "spi_dev_ctl.h"
#include <fcntl.h>
#include <getopt.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <linux/spi/spidev.h>
#include <sys/ioctl.h>

#define SPI1_DEV_PATH "/dev/spidev1.0"
#define CMD_SPI_MODE SPI_MODE_0
#define CMD_SPI_BITS 8
#define CMD_SPI_SPEED 400000
#define CMD_SPI_DELAY 50000 // us

static int spi1_open(void);
static int spi1_transfer(const uint8_t tx[], uint8_t rx[], uint32_t len);
static void spi1_dev_close(void);

spi_dev_t spi1 = {
    .dp_en = false,
    .open = spi1_open,
    .transfer = spi1_transfer,
    .close = spi1_dev_close,
};

static void pabort(const char *s)
{
    perror(s);
    abort();
}

static int Read_spi_configuration(int fd, spi_config_t *config)
{
    uint8_t u8;
    uint32_t u32;

    if (ioctl(fd, SPI_IOC_RD_MODE, &u8) < 0)
    {
        perror("SPI_IOC_RD_MODE");
        return -1;
    }
    config->spi_mode = u8 & 0x03;
    config->spi_ready = ((config->spi_mode & SPI_READY) ? 1 : 0);

    if (ioctl(fd, SPI_IOC_RD_LSB_FIRST, &u8) < 0)
    {
        perror("SPI_IOC_RD_LSB_FIRST");
        return -1;
    }
    config->lsb_first = (u8 == SPI_LSB_FIRST ? 1 : 0);

    if (ioctl(fd, SPI_IOC_RD_BITS_PER_WORD, &u8) < 0)
    {
        perror("SPI_IOC_RD_BITS_PER_WORD");
        return -1;
    }
    config->bits_per_word = (u8 == 0 ? 8 : u8);

    if (ioctl(fd, SPI_IOC_RD_MAX_SPEED_HZ, &u32) < 0)
    {
        perror("SPI_IOC_RD_MAX_SPEED_HZ");
        return -1;
    }
    config->spi_speed = u32;

    return 0;
}

static int wr_spi_cfg(int fd, spi_config_t *config)
{
    uint8_t u8;
    uint32_t u32;

    u8 = config->spi_mode;
    if (config->spi_ready == 1)
        u8 |= SPI_READY;
    if (ioctl(fd, SPI_IOC_WR_MODE, &u8) < 0)
    {
        // perror("SPI_IOC_WR_MODE");
        return -1;
    }

    u8 = (config->lsb_first ? SPI_LSB_FIRST : 0);
    if (ioctl(fd, SPI_IOC_WR_LSB_FIRST, &u8) < 0)
    {
        // perror("SPI_IOC_WR_LSB_FIRST");
        return -1;
    }

    u8 = config->bits_per_word;
    if (ioctl(fd, SPI_IOC_WR_BITS_PER_WORD, &u8) < 0)
    {
        // perror("SPI_IOC_WR_BITS_PER_WORD");
        return -1;
    }

    u32 = config->spi_speed;
    if (ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &u32) < 0)
    {
        // perror("SPI_IOC_WR_MAX_SPEED_HZ");
        return -1;
    }

    return 0;
}

static int spi_open(spi_dev_t *spi_dev, const char *dev_path, spi_config_t *spi_cfg_struct)
{
    // int ret = 0;
    spi_config_t pre_spi_cfg;

    spi_dev->fd = open(dev_path, O_RDONLY);
    if (spi_dev->fd < 0)
    {
        // pabort("can't open device");
        return -2;
    }

    if (Read_spi_configuration(spi_dev->fd, &pre_spi_cfg) != 0)
    {
        // exit(EXIT_FAILURE);
        return -1;
    }

    if (spi_dev->dp_en)
    {
        printf("pre spi mode:0x%x\n", pre_spi_cfg.spi_mode);
        printf("pre spi ready:0x%x\n", pre_spi_cfg.spi_ready);
        printf("pre spi lsb_first:0x%x\n", pre_spi_cfg.lsb_first);
        printf("pre spi bits_per_word:0x%x\n", pre_spi_cfg.bits_per_word);
        printf("pre spi speed:%d\n", pre_spi_cfg.spi_speed);
    }

    if (spi_cfg_struct == NULL) // use pre config
    {
        spi_dev->cfg.spi_mode = pre_spi_cfg.spi_mode;
        spi_dev->cfg.spi_speed = pre_spi_cfg.spi_speed;
        spi_dev->cfg.lsb_first = pre_spi_cfg.lsb_first;
        spi_dev->cfg.spi_ready = pre_spi_cfg.spi_ready;
        spi_dev->cfg.bits_per_word = pre_spi_cfg.bits_per_word;
    }
    else
    { // use new config
        //! todo need Check parameter
        spi_dev->cfg.spi_mode = spi_cfg_struct->spi_mode;
        spi_dev->cfg.lsb_first = spi_cfg_struct->lsb_first;
        spi_dev->cfg.bits_per_word = spi_cfg_struct->bits_per_word;
        spi_dev->cfg.spi_ready = spi_cfg_struct->spi_ready;
        spi_dev->cfg.spi_speed = spi_cfg_struct->spi_speed;

        return wr_spi_cfg(spi_dev->fd, &spi_dev->cfg);
    }

    return 0;
}

static int spi_transfer(spi_dev_t *spi_dev, const uint8_t tx[], uint8_t rx[], uint32_t len)
{
    struct spi_ioc_transfer transfer;
    /*    struct spi_ioc_transfer transfer = {
            .tx_buf = 0,
            .rx_buf = 0,
            .len = 0,
            .delay_usecs = 0,
            .speed_hz = 0,
            .bits_per_word = 0,
        };*/
    memset((void *)&transfer, 0, sizeof(struct spi_ioc_transfer));

    transfer.rx_buf = (unsigned long)rx;
    transfer.tx_buf = (unsigned long)tx;
    transfer.len = len;
    transfer.delay_usecs = CMD_SPI_DELAY;

    if (ioctl(spi_dev->fd, SPI_IOC_MESSAGE(1), &transfer) < 0)
        return -1;

    return 0;
}

static int spi1_open(void)
{
    int ret = 0;
    spi_config_t *new_cfg = NULL; //! HK36B828A spi1 Can't config, set NULL.
    ret = spi_open(&spi1, SPI1_DEV_PATH, new_cfg);
    if (ret < 0)
    {
        if (spi1.dp_en)
        {
            printf("spi open error! err code: %d\n", ret);
        }
    }

    return ret;
}

static void spi1_dev_close(void)
{
    close(spi1.fd);
}

static int spi1_transfer(const uint8_t tx[], uint8_t rx[], uint32_t len)
{
    return spi_transfer(&spi1, tx, rx, len);
}
